using Assets.Scripts;
using Assets.Scripts.Atmospherics;
using Assets.Scripts.Inventory;
using Assets.Scripts.Objects;
using Assets.Scripts.Objects.Clothing;
using Assets.Scripts.Objects.Entities;
using Assets.Scripts.Objects.Items;
using Assets.Scripts.Objects.Pipes;
using Assets.Scripts.UI;
using HarmonyLib;
using System.Collections.Generic;
using VisualmodBep;

namespace StationeersServerPreference {
    public class SspQuickAction {
        public static void OnUpdate() {
            if (!SspConfig.GamePlay.QuickAction.Value) {
                return;
            }
            if (SspConfig.GamePlay.QuickActionToolHotkey.Value.IsDown()) {
                QuickAction();
            } else if (SspConfig.GamePlay.QuickActionSortHotkey.Value.IsDown()) {
                SortInventory();
            }
        }

        // @ref string Entity.PrintDebug()
        // @ref Entity.OnLifeCreated()
        public static void RefreshHuman(Human human = null) {
            if (human == null) {
                human = Util.GetHuman();
                if (human == null) {
                    Log.LogError("RefreshHuman no human");
                    return;
                }
            }
            Log.LogInfo($"RefreshHuman {human.DisplayName}");

            // body
            human.FoodQuality = 1f;  // default 0.75, range [0, 1]
            human.Hydration = 8.75f;  // protected setter, default 5, 5 for 100%, range [0, 8.75]
            human.Hygiene = 1.25f; // > 0.25 is ok, range [0, 1.25]
            AccessTools.PropertySetter(typeof(Human), "Mood").Invoke(human, new object[] { 1f });  // protected setter, max 1
            AccessTools.PropertySetter(typeof(Human), "Nutrition").Invoke(human, new object[] { human.BaseNutritionStorage });  // protected setter
            human.Oxygenation = 0.024f; // Entity.MaxOxygenStorage, unknown
            AccessTools.PropertySetter(typeof(Human), "OxygenQuality").Invoke(human, new object[] { 2f }); // protected setter, >= 1.5 is good
            human.DamageState.Copy(new DamageUpdate(null)); // heal

            // suit
            Suit suit = human.SuitSlot.Get<Suit>();
            if (suit != null) {
                BatteryCell battery = suit.Battery;
                if (battery != null) {
                    battery.PowerStored = battery.PowerMaximum;
                }
                GasCanister airTank = suit.AirTank;
                if (airTank != null) {
                    Atmosphere atmo = airTank.InternalAtmosphere;
                    Log.LogInfo($"AirTank {SspAtmosphere.DebugAtmosphere(atmo)}");
                    SspAtmosphere.RemoveGasAllFromMixture(ref atmo.GasMixture);
                    atmo.RestSetCombustionData();
                    // Log.LogInfo($"    clear all: {SspAtmosphere.DebugAtmosphere(atmo)}");
                    // 5MPa 300K
                    SspAtmosphere.AddGasPVT(atmo, Chemistry.GasType.Oxygen, 5000.0, atmo.Volume.ToDouble(), 300.0);
                    // Log.LogInfo($"    add O2: {SspAtmosphere.DebugAtmosphere(atmo)}");
                }
                GasCanister wasteTank = suit.WasteTank;
                if (wasteTank != null) {
                    Atmosphere atmo = wasteTank.InternalAtmosphere;
                    Log.LogInfo($"WasteTank {SspAtmosphere.DebugAtmosphere(atmo)}");
                    SspAtmosphere.RemoveGasAllFromMixture(ref atmo.GasMixture);
                }
            }
        }

        private static bool ToolIsToolEntry(Item entry, DynamicThing tool) {
            return ToolIsToolEntry(entry, tool as Item);
        }

        // @ref ToolBasic.IsToolEntry
        private static bool ToolIsToolEntry(Item entry, Item tool) {
            if (entry != null && tool != null) {
                if (tool.PrefabHash == entry.PrefabHash) { return true; }
                if (tool.ReplacementOf != null && tool.ReplacementOf.PrefabHash == entry.PrefabHash) { return true; }
            }
            return false;
        }

        public enum SlotResult {
            Already,
            SwapHands,
            SwapInventory,
            SwapInventoryAndHand,
            FoundButFailed,
            NotFound
        };

        public static bool SlotResultGood(SlotResult result) {
            return result != SlotResult.FoundButFailed && result != SlotResult.NotFound;
        }

        private static SlotResult TryFreeHansSlot(Slot hand, List<Slot> slots, string source) {
            SlotResult ret = SlotResult.NotFound;
            foreach (Slot slot in slots) {
                if (slot.Get() == null) continue;
            }
            return ret;
        }

        private static SlotResult FindToActiveHandFromPacks(Item entry, List<Slot> slots, string source) {
            Slot active = InventoryManager.ActiveHandSlot;
            Slot inactive = InventoryManager.Instance.InactiveHand.Slot;
            SlotResult ret = SlotResult.NotFound;
            int i = 0;
            foreach (Slot slot in slots) {
                if (ToolIsToolEntry(entry, slot.Get())) {
                    string name = slot.Get().DisplayName;
                    if (active.Get() == null) {
                        if (inactive.Get() != null && Slot.AllowSwap(slot, inactive)) // swapable in inactive hand
                        {
                            inactive.PlayerSwapToSlot(slot); // swap tools (async)
                            InventoryManager.ParentHuman.SwapHands(); // swap hand afterwards
                            Log.LogInfo($"  Swap {name} from {source}#{i} to inactive hand");
                            return SlotResult.SwapInventoryAndHand;
                        } else // move to active hand
                          {
                            // @note PlayerMoveToSlot will trigger DoSwapActiveHand
                            active.PlayerMoveToSlot(slot.Get()); // KeyResult.SlotToHand
                            Log.LogInfo($"  Grab {name} from {source}#{i} to active hand"); // slot's content has changed
                            return SlotResult.SwapInventory;
                        }
                    } else {
                        if (Slot.AllowSwap(slot, active)) {
                            active.PlayerSwapToSlot(slot); // KeyResult.Swap
                            Log.LogInfo($"  Swap {name} from {source}#{i} to active hand");
                            return SlotResult.SwapInventory;
                        } else if (inactive.Get() == null) {
                            inactive.PlayerMoveToSlot(slot.Get()); // grab tools to empty inactive hand
                            Log.LogInfo($"  Grab {name} from {source}#{i} to inactive hand");
                            return SlotResult.SwapInventoryAndHand;
                        } else if (Slot.AllowSwap(slot, inactive)) {
                            inactive.PlayerSwapToSlot(slot); // swap tools
                            InventoryManager.ParentHuman.SwapHands(); // swap hand afterwards
                            Log.LogInfo($"  Swap {name} from {source}#{i} to inactive hand ({inactive.Get().DisplayName})");
                            return SlotResult.SwapInventoryAndHand;
                        }
                    }
                    Log.LogInfo($"  Find {name} in {source} but cannot swap");
                    ret = SlotResult.FoundButFailed;
                }
                i++;
            }
            return ret;
        }

        // @ref InventoryWindowManager.IsValid
        private static bool FindToActiveHand(Item entry) {
            Human human = InventoryManager.ParentHuman;
            if (human == null) { return false; }

            DynamicThing activeHand = InventoryManager.ActiveHandSlot.Get();
            if (ToolIsToolEntry(entry, activeHand)) {
                Log.LogInfo($"  Active hand already done");
                return true;// already satisfied
            }

            DynamicThing inactiveHand = InventoryManager.Instance.InactiveHand.Slot.Get();
            // find and swap active tool
            if (ToolIsToolEntry(entry, inactiveHand)) {
                Log.LogInfo($"  Active hand swap");
                InventoryManager.ParentHuman.SwapHands(); // swap hands if tool at inactive hand
                return true;
            } else {
                ToolBelt toolBelt = human.ToolbeltSlot.Get() as ToolBelt;
                if (toolBelt != null) {
                    // try find in toolbelt
                    SlotResult result = FindToActiveHandFromPacks(entry, toolBelt.Slots, "ToolBelt");
                    if (SlotResultGood(result)) {
                        return true;
                    } else if (result == SlotResult.FoundButFailed) {
                        // try free active hand
                    }
                }
                Backpack backpack = human.BackpackSlot.Get() as Backpack;
                if (backpack != null) {
                    // try find in toolbelt
                    SlotResult result = FindToActiveHandFromPacks(entry, backpack.Slots, "Backpack");
                    if (SlotResultGood(result)) {
                        return true;
                    }
                }
            }
            return false; // failed
        }

        private static bool FindToInactiveHandFromPacks(Item entry, List<Slot> slots, string source) {
            Slot inactive = InventoryManager.Instance.InactiveHand.Slot;
            foreach (Slot slot in slots) {
                if (ToolIsToolEntry(entry, slot.Get())) {
                    string name = slot.Get().DisplayName;
                    if (inactive.Get() == null) {
                        // @note PlayerMoveToSlot will trigger DoSwapActiveHand
                        inactive.PlayerMoveToSlot(slot.Get()); // move to empty inactive hand
                        // @TODO Eliminate the UI sound effect
                        InventoryManager.ParentHuman.SwapHands(); // swap hand afterwards
                        Log.LogInfo($"  Grab {name} from {source} to inactive hand");
                        return true;
                    }
                    if (Slot.AllowSwap(slot, inactive)) {
                        inactive.PlayerSwapToSlot(slot); // swap to inactive hand
                        Log.LogInfo($"  Swap {name} from {source} to inactive hand");
                        return true;
                    } else {
                        Log.LogInfo($"  Find {name} in {source} but cannot swap");
                    }
                }
            }
            return false;
        }

        // assume that active hand is equipped with demanded tool
        // cannot use active hand
        private static bool FindToInactiveHand(Item entry) {
            Human human = InventoryManager.ParentHuman;
            if (human == null) { return false; }

            DynamicThing inactiveHand = InventoryManager.Instance.InactiveHand.Slot.Get();
            if (ToolIsToolEntry(entry, inactiveHand)) {
                Log.LogInfo($"  Inactive hand already done");
                return true; // already satisfied
            }

            DynamicThing activeHand = InventoryManager.ActiveHandSlot.Get();
            // find and swap active tool
            if (ToolIsToolEntry(entry, activeHand)) {
                Log.LogInfo($"  Inactive hand swap");
                InventoryManager.ParentHuman.SwapHands(); // swap hands if things at active hand
                return true;
            } else {
                ToolBelt toolBelt = human.ToolbeltSlot.Get() as ToolBelt;
                if (toolBelt != null) {
                    // try find in toolbelt
                    if (FindToInactiveHandFromPacks(entry, toolBelt.Slots, "ToolBelt")) { return true; }
                }
                Backpack backpack = human.BackpackSlot.Get() as Backpack;
                if (backpack != null) {
                    // try find in toolbelt
                    if (FindToInactiveHandFromPacks(entry, backpack.Slots, "Backpack")) { return true; }
                }
            }
            return false; // failed
        }

        // @ref InventoryManager.HandlePrimaryUse
        // @ref InventoryWindowManager.SinglePressInteraction
        // @ref Structure.HandleToolUse
        // @ref Pipe.IsBurst
        // @ref ToolBasic.GetRepairsAsString
        public static void QuickAction() {
            // if (CursorManager.CursorThing == null) { return; }
            Structure structure = CursorManager.CursorThing as Structure;
            if (structure == null) {
                return;
            }
            Log.LogInfo($"QuickAction {structure.DisplayName}${structure.ReferenceId.ToString("X")}");
            if (structure.BuildStates.Count <= 0) {
                return;
            }

            int index = structure.CurrentBuildStateIndex;
            int length = structure.BuildStates.Count;
            PrintBuildStateInfo(structure);

            if (structure.DamageState.TotalRatioClamped > 0f) {
                TrySelectRepair(structure); // try select repair tools for damaged state
            } else if (structure.IsBroken) {
                TrySelectDeconstruction(structure); // try select deconstruction tools for borken state
            } else if (index + 1 < length) {
                TrySelectConstruction(structure); // try select construction tools for next state
            } else {
                TrySelectDeconstruction(structure); // try select deconstruction tools for current state
            }
        }

        private static void PrintBuildStateInfo(Structure structure) {
            int index = structure.CurrentBuildStateIndex;
            int length = structure.BuildStates.Count;
            string current = ToolString(structure.CurrentBuildState?.Tool);
            string next = ToolString(structure.NextBuildState?.Tool);
            string repair = ToolString(structure.RepairTools);
            Log.LogInfo($"  BuildStates {index + 1}/{length} C{current} N{next} R{repair}");
        }

        private static void TrySelectConstruction(Structure structure) {
            ToolUse tool = structure.NextBuildState?.Tool;
            if (tool == null) {
                Log.LogError(" TrySelectConstruction no tool");
                return;
            }
            FindToActiveHand(tool.ToolEntry);
            if (tool.ToolEntry2 != null) {
                // try select construction materials
                FindToInactiveHand(tool.ToolEntry2);
            }
        }

        private static void TrySelectDeconstruction(Structure structure) {
            ToolUse tool = structure.CurrentBuildState?.Tool;
            if (tool == null) {
                Log.LogError(" TrySelectDeconstruction no tool");
                return;
            }
            FindToActiveHand(tool.ToolExit);
        }

        private static void TrySelectRepair(Structure structure) {
            ToolBasic tool = structure.RepairTools;
            if (tool == null) {
                Log.LogError(" TrySelectRepair no tool");
                return;
            }
            FindToActiveHand(tool.ToolEntry);
            if (tool.ToolEntry2 != null) {
                // try select repair materials
                FindToInactiveHand(tool.ToolEntry2);
            }
        }

        private static string ToolString(ToolBasic tool) {
            if (tool == null) {
                return "-";
            }
            string str = "";
            if (tool.ToolEntry as Stackable != null) {
                str += $"{tool.EntryQuantity}x{tool.ToolEntry.DisplayName}";// primary kits
            } else if (tool.ToolEntry != null) {
                str += tool.ToolEntry.DisplayName; // primary tool
            } else {
                str += '?';
            }
            str += '+';
            if (tool.ToolEntry2 as Stackable != null) {
                str += $"{tool.EntryQuantity2}x{tool.ToolEntry2.DisplayName}"; // secondary materials
            } else if (tool.ToolEntry2 != null) {
                str += tool.ToolEntry2.DisplayName; // secondary tool (maybe?)
            } else {
                str += '?';
            }
            ToolUse toolUse = tool as ToolUse;
            if (toolUse != null) {
                str += '+';
                Item toolExit = toolUse.ToolExit;
                if (toolExit as Stackable != null) {
                    str += $"{toolUse.ExitQuantity}x{toolExit.DisplayName}"; // deconstruct materials (maybe?)
                } else if (toolExit != null) {
                    str += toolExit.DisplayName; // deconstruct tool
                } else {
                    str += '?';
                }
            }
            return '<' + str + '>';
        }

        public static void SortInventory() {

        }
    }

    namespace HarmonyPatcher {
        public class PatcherQuickAction {
            private static string previousMessage;
            private static int count = 0;

            [HarmonyPatch(typeof(InventoryManager), "HandlePrimaryUse")]
            [HarmonyPostfix]
            static void Patch_InventoryManager_HandlePrimaryUse(InventoryManager __instance) {
                Item item = __instance.ActiveHand.Slot.Get() as Item;
                if (item == null) {
                    return;
                }
                if (CursorManager.CursorThing) {
                    Thing thing = CursorManager.CursorThing;
                    string message = $"  {item.DisplayName} -> {thing.DisplayName}${thing.ReferenceId.ToString("X")}";
                    StoreLog(message);
                } else {
                    // Log.LogInfo($"  {item.DisplayName} UseItemOnSelf");
                }
            }

            private static void StoreLog(string message) {
                if (message == previousMessage) {
                    count++;
                } else {
                    FlushLog(message);
                }
            }

            public static void FlushLog(string message) {
                if (count > 0) {
                    Log.LogInfo($"{previousMessage} x{count}");
                    count = 0;
                }
                if (!string.IsNullOrEmpty(message)) {
                    Log.LogInfo($"POST InventoryManager.HandlePrimaryUse");
                    Log.LogInfo(message);
                }
                previousMessage = message;
            }
        }
    }
}
